#include "servernntp.h"
#include "connectionclosedexception.h"
#include <algorithm>
#include <sstream>

#ifdef DEBUG
#include <iostream>
#define trace if(false) cout
#endif

using namespace std;
/* change new Database() for new DatabaseDisk if one wish to have a persistant database*/
#ifdef DISC
ServerNNTP::ServerNNTP(): the_server(5001), ng(new DatabaseDisk()) 
{
	cout << "Disc Server" << endl;
}
#else
ServerNNTP::ServerNNTP():the_server(5001), ng(new Database()) 
{
	cout << "Memory Server" << endl;
}
#endif
void ServerNNTP::destroy(void)
{
	delete ng;
}

void ServerNNTP::run()
{
	string 	input;
	string 	output;
		
	while (true) {
		connection = the_server.waitForActivity();
		if (connection == 0) {
			trace << "new connection" << endl;
			the_server.registerConnection(new Connection);
			continue;
		}
		try {
			trace << "receive method called message size: " << input.size() <<  endl;	
			receive(output, connection);

			trace << "output size: " << output.size() << endl;
			for (size_t i = 0; i < output.size(); ++i) {
				connection->write(output[i]);
			}
			output.clear();
			trace << "after write" << endl;

		} catch (ConnectionClosedException e) {
			the_server.deregisterConnection(connection);	
			trace << "ConnectionClosedException" << endl;
			delete connection;
		}
	}

}

void ServerNNTP::ack(string &ans)
{
	ans += Protocol::ANS_ACK;
}

void ServerNNTP::nack_not_art(string &ans)
{
	ans += Protocol::ANS_NAK;
	ans += Protocol::ERR_ART_DOES_NOT_EXIST;
}

void ServerNNTP::nack_already_ng(string &ans)
{
	ans += Protocol::ANS_NAK;
	ans += Protocol::ERR_NG_ALREADY_EXISTS;
}

void ServerNNTP::nack_not_ng(string &ans)
{
	ans += Protocol::ANS_NAK;
	ans += Protocol::ERR_NG_DOES_NOT_EXIST;
}


/* the params are expected to be in the order they are used according to
	the protocol */
bool ServerNNTP::answer(string& ans, 
			vector<string>& s_param, 
			vector<unsigned int>& n_param
			)
{
	s_param.front();
	map<unsigned int, NewsGroup*>::iterator itr;
	string					s_tmp;
	NewsGroup*				ng_tmp;
	Article*				art_tmp;
	unsigned int 				tmp;
	trace << "ans_start" << endl;
	switch (ans[0]) {
		case Protocol::ANS_LIST_NG: 
			trace << "ANS_LIST_NG:" << endl;
			//should create "num_p [num_p string_p]*"
			tmp = ng->size();
			add_par_num(ans, tmp);
			for (itr = ng->begin(); itr != ng->end(); ++itr){
				add_par_num(ans, itr->first);
				tmp = itr->second->get_title().size();
				s_tmp = itr->second->get_title();
				add_par_string(ans, tmp, s_tmp);
			}
		break;	
		case Protocol::ANS_CREATE_NG:
		//should create "ANS_NAK ERR_NG_ALREADY_EXISTS" or "ANS_ACK"
			trace << "ANS_CREATE_NG" << endl;
			if (find_if(ng->begin(), ng->end(), EqPred(s_param.front()))  == ng->end()
			) {
				ng_tmp = new NewsGroup(s_param.front());
				trace << "newsgroup created: id" << ng_tmp->get_id() << endl; 
				ng->insert(ng_tmp->get_id(), ng_tmp);
				ack(ans);	
			} else {
				trace << "newsgroup already exists" << endl;
				nack_already_ng(ans);
			}
		
		break;
		case Protocol::ANS_DELETE_NG:
			trace << "ANS_DELETE_NG: " << n_param.front() <<  endl;
			if ((itr = ng->find(n_param.front())) == ng->end()) {
				nack_not_ng(ans);
			} else {
				trace << "deleted" << endl;
				delete itr->second;
				ng->erase(itr);
				ack(ans);
			}
		break;
		case Protocol::ANS_LIST_ART:
			trace << "ANS_LIST_ART" << endl;
			if (!n_param.size()) {
				return false;
			} else if ((itr = ng->find(n_param.front())) == ng->end()) {
				nack_not_ng(ans);
			} else {	
				ack(ans); 
				map<unsigned int, Article*>::iterator itrr;
				ng_tmp = itr->second;
				tmp = itr->second->size();
				add_par_num(ans, tmp);
				for (itrr = ng_tmp->begin(); itrr != ng_tmp->end(); ++itrr){
					add_par_num(ans, itrr->first);
					s_tmp = itrr->second->title;
					tmp = s_tmp.size();
					add_par_string(ans, tmp, s_tmp);
				}
			}
		break;	
		case Protocol::ANS_CREATE_ART:
			trace << "ANS_CREATE_ART: "<< endl;
			if (ng->find(n_param.front()) == ng->end()) {
				nack_not_ng(ans);
			} else { 
				(*ng)[n_param.front()]->add_article(
					s_param[0], s_param[2], s_param[1]);
					ng->update();
					ack(ans);
			}
		break;
		case Protocol::ANS_DELETE_ART:
			trace << "ANS_DELETE_ART" << endl;
			if (ng->find(n_param.front()) == ng->end()) {
				nack_already_ng(ans);
				break;
			} 
			if (!(*ng)[n_param[0]]->delete_article(n_param[1])) {
				return false;
			} else { 
				ack(ans);
				ng->update();
			} 
		break;
		case Protocol::ANS_GET_ART:
			cout << "ANS_GET_ART: " << n_param.front() << endl;
			if ((itr = ng->find(n_param.front())) == ng->end()) {
				nack_not_ng(ans);	
			} else if (itr->second->get_article(&
					art_tmp, n_param[1])) {
				ack(ans);	
				//title
				s_tmp = art_tmp->title;
				tmp = s_tmp.size();
				add_par_string(ans, tmp, s_tmp); 
				//author
				s_tmp = art_tmp->author;
				tmp = s_tmp.size();
				add_par_string(ans, tmp, s_tmp);
				//text
				s_tmp = art_tmp->text;
				tmp = s_tmp.size();
				add_par_string(ans, tmp, s_tmp);
							
			} else {
				nack_not_art(ans);
			}
		break;	

		default: trace << "default in answer" << endl;
			return false; 
	}
			
	trace << "answer created without failure." << endl;
	ans += Protocol::ANS_END;	
	return true;
}


bool ServerNNTP::receive(string& ans, Connection* connection) 
{
	vector<string> 		s_param;
	vector<unsigned int>	n_param;
	string 			tmp;
	char 			input;
	unsigned int 		integer;
	unsigned char		c1, c2, c3, c4;

	while (1) {
		input = connection->read();
		switch (input) {
			case Protocol::COM_LIST_NG:  	
				ans += Protocol::ANS_LIST_NG;	
				trace << "COM_LIST_NG" << endl;
			break;
			case Protocol::COM_CREATE_NG:
				ans += Protocol::ANS_CREATE_NG;
			break;
			case Protocol::COM_DELETE_NG:
				trace << "COM_DELETE_NG" << endl; ans += Protocol::ANS_DELETE_NG;
			break;
			case Protocol::COM_CREATE_ART:
				ans += Protocol::ANS_CREATE_ART;
				trace << "COM_CREATE_ART" << endl;
			break;
			case Protocol::COM_LIST_ART:
				trace << "COM_LIST_ART" << endl;
				ans += Protocol::ANS_LIST_ART;
			break;
			case Protocol::COM_GET_ART:
				trace << "COM_GET_ART" << endl;
				ans += Protocol::ANS_GET_ART;
			break;
			case Protocol::COM_END:
				trace << "COM_END" << endl;
				if(!answer(ans, s_param, n_param)) { 
					trace << "answer FAIL: " << __LINE__ << endl;
					throw ConnectionClosedException();
				}

				return true;
			break;
			case Protocol::COM_DELETE_ART:
				trace << "COM_DELETE_ART" << endl;
				ans += Protocol::ANS_DELETE_ART;
			break;
			case Protocol::PAR_NUM:
				trace << "PAR_NUM" << endl;
				c1 = connection->read();
				c2 = connection->read();
				c3 = connection->read();
				c4 = connection->read();

				integer = (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
/*				for (size_t i = 0; i < N_SIZE; ++i) {
					integer |= (connection->read() << ((N_SIZE - i - 1) * 8));	
				}*/
				trace << "parnum: " << integer << endl;
				n_param.push_back(integer);
				//trace << "i after parnum " << i << endl;
			break;
			case Protocol::PAR_STRING:
				trace << "PAR_STRING" << endl;
				c1 = connection->read();
				c2 = connection->read();
				c3 = connection->read();
				c4 = connection->read();

				integer = (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
		/*		integer = 0;
				for (size_t i = 0; i < N_SIZE; ++i) {
					integer |= (connection->read() << ((N_SIZE - i - 1) * 8));	
				}
			*/
				for (size_t i = 0; i < integer; ++i) {
					tmp += connection->read();				
				}
				s_param.push_back(tmp);
				tmp.clear();
			break;	
			default:
				trace << "default: " << __LINE__ << endl; 
				throw ConnectionClosedException(); 
				// just ignore the message
			break;
		}
	}
	return true;
}


